/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "r_local.h"


#define SHADERS_HASH_SIZE				MAX_SHADERS / 4

static shader_t *		r_shadersHashTable[SHADERS_HASH_SIZE];
static shader_t *		r_shaders[MAX_SHADERS];
static int				r_numShaders;


/*
 ==============================================================================

 SHADER LOADING

 ==============================================================================
*/


/*
 ==================
 R_CompileShader
 ==================
*/
static void R_CompileShader (const char *source, shader_t *shader){

	char	infoLog[MAX_PRINT_MESSAGE];
	int		infoLogLen;
	int		compileStatus;

	// Create the shader
	switch (shader->type){
	case SHADER_VERTEX:
		shader->shaderId = qglCreateShader(GL_VERTEX_SHADER);
		break;
	case SHADER_FRAGMENT:
		shader->shaderId = qglCreateShader(GL_FRAGMENT_SHADER);
		break;
	default:
		Com_Error(false, "R_CompileShader: bad shader type (%i)", shader->type);
	}

	// Upload the shader source
	qglShaderSource(shader->shaderId, 1, &source, NULL);

	// Compile the shader
	qglCompileShader(shader->shaderId);

	// Check for errors and print any to the console
	qglGetShaderiv(shader->shaderId, GL_COMPILE_STATUS, &compileStatus);
	if (!compileStatus){
		switch (shader->type){
		case SHADER_VERTEX:
			Com_Printf(S_COLOR_RED "Failed to compile vertex shader '%s'\n", shader->name);
			break;
		case SHADER_FRAGMENT:
			Com_Printf(S_COLOR_RED "Failed to compile fragment shader '%s'\n", shader->name);
			break;
		}

		qglGetShaderiv(shader->shaderId, GL_INFO_LOG_LENGTH, &infoLogLen);
		if (infoLogLen > 0){
			qglGetShaderInfoLog(shader->shaderId, sizeof(infoLog), NULL, infoLog);

			Com_DPrintf("------------------------------\n");
			Com_DPrintf("%s\n", infoLog);
			Com_DPrintf("------------------------------\n");
		}

		shader->compiled = false;

		return;
	}

	shader->compiled = true;
}

/*
 ==================
 R_LoadShader
 ==================
*/
static void R_LoadShader (const char *name, shaderType_t type, const char *source){

	shader_t	*shader;
	uint		hashKey;

	if (r_numShaders == MAX_SHADERS)
		Com_Error(false, "R_LoadShader: MAX_SHADER hit");

	r_shaders[r_numShaders++] = shader = (shader_t *)Mem_Alloc(sizeof(shader_t), TAG_RENDERER);

	// Fill it in
	Str_Copy(shader->name, name, sizeof(shader->name));
	shader->type = type;
	shader->references = 0;

	R_CompileShader(source, shader);

	// Add to hash table
	hashKey = Str_HashKey(shader->name, SHADERS_HASH_SIZE, false);

	shader->nextHash = r_shadersHashTable[hashKey];
	r_shadersHashTable[hashKey] = shader;
}

/*
 ==================
 R_FindShader
 ==================
*/
shader_t *R_FindShader (const char *name, shaderType_t type){

	shader_t	*shader;
	uint		hashKey;

	// See if already loaded
	hashKey = Str_HashKey(name, SHADERS_HASH_SIZE, false);

	for (shader = r_shadersHashTable[hashKey]; shader; shader = shader->nextHash){
		if (!shader->compiled || shader->type != type)
			continue;

		if (!Str_ICompare(shader->name, name))
			return shader;
	}

	// Not found
	return NULL;
}


// ============================================================================


/*
 ==================
 R_ListShaders_f
 ==================
*/
static void R_ListShaders_f (void){

	shader_t	*shader;
	int			i;

	Com_Printf("\n");
	Com_Printf("      type refs -name--------\n");

	for (i = 0; i < r_numShaders; i++){
		shader = r_shaders[i];

		Com_Printf("%4i: ", i);

		switch (shader->type){
		case SHADER_VERTEX:
			Com_Printf("vert ");
			break;
		case SHADER_FRAGMENT:
			Com_Printf("frag ");
			break;
		default:
			Com_Printf("???? ");
			break;
		}

		Com_Printf("%4i ", shader->references);

		Com_Printf("%s%s\n", shader->name, (!shader->compiled) ? " (INVALID)" : "");
	}

	Com_Printf("------------------------\n");
	Com_Printf("%i total shaders\n", r_numShaders);
	Com_Printf("\n");
}

/*
 ==================
 R_InitShaders
 ==================
*/
void R_InitShaders (void){

	char		*source;
	char		name[MAX_OSPATH];
	const char	**fileList;
	int			numFiles;
	int			i;

	Com_Printf("Initializing Shaders\n");

	// Register our commands
	Cmd_AddCommand("listShaders", R_ListShaders_f, "Lists loaded shaders");

	// Load .vs files
	fileList = FS_ListFiles("shaders", ".vs", true, &numFiles);

	for (i = 0; i < numFiles; i++){
		// Load the source file
		Str_SPrintf(name, sizeof(name), "shaders/%s", fileList[i]);
		Com_Printf("...loading '%s'", name);

		FS_ReadFile(name, (void **)&source);
		if (!source){
			Com_Printf(": failed\n");
			continue;
		}
		Com_Printf("\n");

		// Compile it
		R_LoadShader(fileList[i], SHADER_VERTEX, source);

		FS_FreeFile(source);
	}

	FS_FreeFileList(fileList);

	// Load .fs files
	fileList = FS_ListFiles("shaders", ".fs", true, &numFiles);

	for (i = 0; i < numFiles; i++){
		// Load the source file
		Str_SPrintf(name, sizeof(name), "shaders/%s", fileList[i]);
		Com_Printf("...loading '%s'", name);

		FS_ReadFile(name, (void **)&source);
		if (!source){
			Com_Printf(": failed\n");
			continue;
		}
		Com_Printf("\n");

		// Compile it
		R_LoadShader(fileList[i], SHADER_FRAGMENT, source);

		FS_FreeFile(source);
	}

	FS_FreeFileList(fileList);
}

/*
 ==================
 R_ShutdownShaders
 ==================
*/
void R_ShutdownShaders (void){

	shader_t	*shader;
	int			i;

	// Unregister our commands
	Cmd_RemoveCommand("listShaders");

	// Delete all shaders
	for (i = 0; i < r_numShaders; i++){
		shader = r_shaders[i];

		qglDeleteShader(shader->shaderId);
	}

	// Clear shader list
	memset(r_shadersHashTable, 0, sizeof(r_shadersHashTable));
	memset(r_shaders, 0, sizeof(r_shaders));

	r_numShaders = 0;
}
